using System;
using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using System.Runtime.CompilerServices;

using Evaluant.Uss;
using Evaluant.Uss.Collections;
using Evaluant.Uss.Commands;
using Evaluant.Uss.ObjectContext.DynamicProxy;
using Evaluant.Uss.ObjectContext.Descriptors;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Diagnostics;
using Evaluant.Uss.Common;
using System.Text;
using System.ComponentModel;
using Evaluant.Uss.ObjectContext.Services;

namespace Evaluant.Uss.ObjectContext
{
	public delegate void EussEventHandler(object sender, EntityEventArgs e);
	public delegate void EussCancelEventHandler(object sender, CancelEntityEventArgs e);
	public delegate void EussReferenceEventHandler(object sender, ReferenceEventArgs e);
	public delegate void EussCancelReferenceEventHandler(object sender, ReferenceCancelEventArgs e);

	public class ObjectContext : IDisposable, IServiceProvider
	{
		public event EussEventHandler ObjectLoaded;

		public event EussCancelEventHandler ObjectUpdating;
		public event EussEventHandler ObjectUpdated;

		public event EussCancelEventHandler ObjectInserting;
		public event EussEventHandler ObjectInserted;

		public event EussCancelEventHandler ObjectDeleting;
		public event EussEventHandler ObjectDeleted;

		public event EussCancelReferenceEventHandler ObjectRelationCreating;
		public event EussReferenceEventHandler ObjectRelationCreated;

		public event EussCancelReferenceEventHandler ObjectRelationRemoving;
		public event EussReferenceEventHandler ObjectRelationRemoved;

		public event EussEventHandler TransactionOpened;
		public event EussEventHandler TransactionClosed;

		private Transaction _CurrentTransaction;
		private ObjectService _Factory;
		private IPersistenceEngine _PersistenceEngine;
		private ReflectionEntityGetter _EntityGetter;
		private Dictionary<Type, FieldInfo> _FieldsInfoCache = new Dictionary<Type, FieldInfo>();
		private HashedList _PendingObjects;
		private Dictionary<int, Entity> _GlobalProcessedObjects = new Dictionary<int, Entity>(); // Indexed by the hashcode not to maintain a string relationship on the object
		private bool _EnableLazyLoading;
		private bool _EnableEvents = false;

		private static readonly string SWITCH_OPATH = "Evaluant.Uss.OPath";
		private static readonly string SWITCH_OPATH_DESC = "Evaluant.Uss.OPath";
		private static readonly string SWITCH_OPATH_DEFAULT = "0";

		protected BooleanSwitch oPathSwitch = new BooleanSwitch(SWITCH_OPATH, SWITCH_OPATH_DESC, SWITCH_OPATH_DEFAULT);

		/// <summary>
		/// Gets or sets the persistence engine.
		/// </summary>
		/// <value></value>
		public IPersistenceEngine PersistenceEngine
		{
			get { return _PersistenceEngine; }
			set { _PersistenceEngine = value; }
		}

		internal bool EnableLazyLoading
		{
			get { return _EnableLazyLoading; }
		}

		internal ObjectContext(ObjectService factory, IPersistenceEngine engine)
		{
			_Factory = factory;
			_PersistenceEngine = engine;
			_EntityGetter = new ReflectionEntityGetter(_Factory.PersistentDescriptor, _PersistenceEngine.Model);
			_EnableLazyLoading = true;
			foreach (Operation operation in Enum.GetValues(typeof(Operation)))
			{
				ServicesCollection collection = new ServicesCollection();
				ServicesCollection parentCollection = factory.GetServices(operation);
				if (parentCollection != null)
					collection.Add(parentCollection);
				services.Add(operation, collection);
			}
		}

		/// <summary>
		/// Gets or sets the parent factory.
		/// </summary>
		/// <value></value>
		public ObjectService Factory
		{
			get { return _Factory; }
			set { _Factory = value; }
		}

		public bool EnableEvents
		{
			get { return _EnableEvents; }
			set { _EnableEvents = value; }
		}

		public static string ConvertNamespaceDomainToEuss(Type type)
		{
			StringBuilder sb = new StringBuilder();

			if (type.IsGenericType)
			{
				string genericType = type.GetGenericTypeDefinition().FullName;
				sb.Append(ConvertNamespaceDomainToEuss(genericType.Substring(0, genericType.IndexOf('`'))));

				foreach (Type parameterType in type.GetGenericArguments())
				{
					sb.Append("{");
					sb.Append(ConvertNamespaceDomainToEuss(parameterType));
					sb.Append("}");
				}
			}
			else
			{
				sb.Append(ConvertNamespaceDomainToEuss(type.FullName));
			}

			return sb.ToString();
		}

		/// <summary>
		/// Converts the namespace domain to euss.
		/// </summary>
		/// <param name="ns">The namespace to convert.</param>
		/// <returns></returns>
		public static string ConvertNamespaceDomainToEuss(string ns)
		{
			char[] chars = ns.ToCharArray();

			for (int i = 0; i < chars.Length; i++)
			{
				if (chars[i] == Constants.DOT)
				{
					chars[i] = Constants.COMMA;
				}
			}

			return new string(chars);
		}

		/// <summary>
		/// Converts the namespace domain to euss and add full entity name if necessary.
		/// </summary>
		/// <param name="ns">The namespace to convert.</param>
		/// <returns></returns>
		internal static string ConvertNamespaceDomainToEuss(Type entityType, string opathConstraint)
		{
			// if opath with more than one entity (ie: A[a=1].B[b=2].C), type to load is C, then just add namespace for A.
			// if opath with just one entity (ie: [a=1]), type to load is A, then add namespace with entityName for A.
			// This assumes that all entity have the same namespace
			string firstEntityOrNamespace = (opathConstraint.StartsWith("[") || opathConstraint.StartsWith(".") || opathConstraint.Length == 0) ? entityType.FullName : String.Concat(entityType.Namespace, ".");
			string convertedEntityOrNameSpace = ConvertNamespaceDomainToEuss(firstEntityOrNamespace);
			if (opathConstraint.StartsWith(convertedEntityOrNameSpace))
				return opathConstraint;
			return String.Concat(convertedEntityOrNameSpace, opathConstraint);
		}

		/// <summary>
		/// Converts the namespace euss to domain.
		/// </summary>
		/// <param name="ns">The euss namespace to convert.</param>
		/// <returns></returns>
		public static string ConvertNamespaceEussToDomain(string ns)
		{
			char[] chars = ns.ToCharArray();

			for (int i = 0; i < chars.Length; i++)
			{
				if (chars[i] == Constants.COMMA)
				{
					chars[i] = Constants.DOT;
				}
			}

			return new string(chars);
		}

		public Entity ExtractEntity(object item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			if (item is IPersistable)
				return ((IPersistable)item).Entity;

			Dictionary<object, Entity> local = new Dictionary<object, Entity>();

            _EnableLazyLoading = false;

			Entity e = _EntityGetter.Get(item, local, _GlobalProcessedObjects);

			TrackLocallyProcessed(local);

            _EnableLazyLoading = true;

			return e;
		}

		/// <summary>
		/// Updates an Entity with an external list.
		/// </summary>
		/// <remarks>
		/// Only used by dynamic proxies
		/// </remarks>
		/// <param name="parent">The parent.</param>
		/// <param name="role">The role.</param>
		/// <param name="values">The values.</param>
		public void UpdateEntitySet(IPersistableProxy parent, string role, IEnumerable values)
		{
			// don't load reference if not needed
			if (!_EnableLazyLoading && !parent.Entity.InferredReferences.Contains(role))
				return;

			HashedList list = new HashedList();

			foreach (object item in values)
			{
				if (item is IPersistableProxy)
				{
					((IPersistableProxy)item).Update();
				}

				list.Add(ExtractEntity(item));
			}

			EntitySet currentList = parent.Entity.GetEntitySet(role);

			// Detect removed relationships
			foreach (Entity e in currentList)
				if (!list.Contains(e))
					parent.Entity.DeleteElement(role, e);

			// Add new relationships
			foreach (Entity e in list)
				if (!currentList.Contains(e))
				{
					parent.Entity.AddValue(role, e);
				}

		}

		/// <summary>
		/// Serializes the specified persistable object.
		/// </summary>
		/// <param name="persistable">Persistable object.</param>
		public void Serialize(IPersistable persistable)
		{
			if (persistable == null)
				throw new ArgumentNullException("persistable");

			if (persistable.Entity == null)
				throw new NullReferenceException("Entity cannot be null");

			EnsureTransactionCreated();

			_CurrentTransaction.Serialize(persistable.Entity);

			// Adds the Entity to the cache after Serialize for it can throw an exception or change the Id's value
			TrackObject(persistable);
		}

		/// <summary>
		/// Serializes the specified object.
		/// </summary>
		/// <param name="item">Object to serialize.</param>
		public void Serialize(object item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			EnsureTransactionCreated();

			// Stores the object in a temporary list to extract its entity during CommitTransaction
			if (!_PendingObjects.Contains(item))
			{
				if (_GlobalProcessedObjects.ContainsKey(item.GetHashCode()))
					_GlobalProcessedObjects[item.GetHashCode()].State = State.Modified;
				_PendingObjects.Add(item);
			}

			IPersistable persistable = item as IPersistable;

			if (persistable != null)
			{
				// Adds the Entity to the cache after Serialize for it can throw an exception or change the Id's value
				TrackObject(persistable);
			}
		}

		/// <summary>
		/// Deletes the specified object.
		/// </summary>
		/// <param name="item">Object to delete.</param>
		public void Delete(object o)
		{


			if (o == null)
				throw new ArgumentNullException("o");

			IPersistable persistable = o as IPersistable;

			if (persistable == null && !_GlobalProcessedObjects.ContainsKey(RuntimeHelpers.GetHashCode(o)))
				throw new ArgumentException("This object is not persisted.");

			Entity e = persistable == null ? (Entity)_GlobalProcessedObjects[RuntimeHelpers.GetHashCode(o)] : persistable.Entity;

			EnsureTransactionCreated();

			UnTrackObject(e.Type, e.Id);
			_CurrentTransaction.Delete(e);
		}


		/// <summary>
		/// Deletes the specified object with the specified Id.
		/// </summary>
		/// <param name="id">Object Id to delete.</param>
		public void DeleteWithId<T>(string id)
		{
			DeleteWithId(typeof(T), id);
		}

		/// <summary>
		/// Deletes the specified object with the specified Id.
		/// </summary>
		/// <param name="type">Object type to delete.</param>
		/// <param name="id">Object Id to delete.</param>
		public void DeleteWithId(Type type, string id)
		{
			EnsureTransactionCreated();

			string eussNamespace = ObjectContext.ConvertNamespaceDomainToEuss(type, "");

			UnTrackObject(eussNamespace, id);
			DeleteEntityCommand dec = new DeleteEntityCommand(id, eussNamespace);
			_CurrentTransaction.PushCommand(dec);
		}


		/// <summary>
		/// Serializes the specified persistable objects.
		/// </summary>
		/// <param name="persistables">Persistable objects.</param>
		public void Serialize(IEnumerable persistables)
		{
			if (persistables == null)
				throw new ArgumentNullException("persistables");

			foreach (object persistable in persistables)
				Serialize(persistable);
		}

		/// <summary>
		/// Serializes the specified persistable objects.
		/// </summary>
		/// <param name="persistables">Persistable objects.</param>
		public void Serialize<T>(IEnumerable<T> persistables)
		{
			if (persistables == null)
				throw new ArgumentNullException("persistables");

			foreach (T persistable in persistables)
				Serialize(persistable);
		}

		/// <summary>
		/// Deletes the specified persistable objects.
		/// </summary>
		/// <param name="persistables">Persistable objects.</param>
		public void Delete<T>(IEnumerable<T> persistables)
		{
			if (persistables == null)
				throw new ArgumentNullException("persistables");

			foreach (T persistable in persistables)
				Delete(persistable);
		}

		/// <summary>
		/// Disposes this instance.
		/// </summary>
		public void Dispose()
		{
			Clear();
		}

		/// <summary>
		/// Initializes the repository.
		/// </summary>
		public void InitializeRepository()
		{
			_PersistenceEngine.InitializeRepository();

			// Empties the cache
			Clear();
		}

		// Contains IdentityMap instances indexed by the Type of the objects they contain
		private Dictionary<string, IIdentityMap> _TypeTable = new Dictionary<string, IIdentityMap>();

		/// <summary>
		/// Removes all IPersistable from the cache
		/// </summary>
		/// <remarks>
		/// All removes IPersistable are disconnected from the ObjectContext.
		/// </remarks>
		public void Clear()
		{
			foreach (IdentityMap im in _TypeTable.Values)
			{
				im.Clear();
			}

			_GlobalProcessedObjects.Clear();

			_TypeTable.Clear();
		}

		/// <summary>
		/// Removes an object from the first-level cache.
		/// </summary>
		/// <param name="persistable">Persistable object.</param>
		public void RemoveFromCache(object persistable)
		{
			if (persistable == null)
				throw new ArgumentNullException("persistable");

			Entity e = null;

			if (_GlobalProcessedObjects.ContainsKey(RuntimeHelpers.GetHashCode(persistable)))
			{
				e = _GlobalProcessedObjects[RuntimeHelpers.GetHashCode(persistable)];
			}

			if (_TypeTable.ContainsKey(e.Type)) // In 1.1 an Hashtable returns null if the key is not found ... not in 2.0
			{
				IIdentityMap im = _TypeTable[e.Type];

				if (persistable is IPersistable)
				{
					((IPersistable)persistable).ObjectContext = null;
				}
				im.Remove(e.Id);
			}
		}

		protected Dictionary<string, Type> _TypeChache = new Dictionary<string, Type>(20);

		/// <summary>
		/// Returns a object object wrapping a given Entity from the internal cache or creates a new one
		/// </summary>
		/// <param name="entity">The entity to wrap</param>
		/// <returns>The object object wrapping an entity</returns>
		protected object GetInstance(Entity entity)
		{
			// Search this Entity in the cache
			object tracked = GetTrackedObject(entity.Type, entity.Id);

			// If the entity has not yet been cached, create a wrapper on it and cache it
			if (tracked == null)
			{
				IPersistable persistable;

				// Search for the business type corresponding to this entity
				Type type = _Factory.GetDomainType(entity.Type);

				// If no type was found, throw an exception
				if (type == null)
					throw new UniversalStorageException("Type not found: " + ConvertNamespaceEussToDomain(entity.Type) + "\nPlease use AddAssembly() to resolve the type's location");

				// Gets a dynamic proxy instance if the class doesn't implement IPersistable directly
				if (typeof(IPersistable).IsAssignableFrom(type))
				{
					persistable = (IPersistable)ActivatorFactory.CreateActivator(type).CreateInstance();
					persistable.Entity = entity;
					tracked = persistable;
				}
				else
				{
					persistable = _Factory.PersistableProxyFactory.CreatePersistableProxy(type);
					persistable.Entity = entity;

					persistable.ObjectContext = this;
					((IPersistableProxy)persistable).Set();

					tracked = persistable;
				}

				// Add the newly created business object to the cache
				TrackObject(persistable);
			}

			if (tracked is IPersistableProxy)
			{
				// Set the entity again so that the references can be loaded from this one in case 
				// the tracked comes from the first level cache

				((IPersistableProxy)tracked).Entity = entity;
				((IPersistableProxy)tracked).SetReferences();
			}

			// The item is already into the cache or has been created
			return tracked;
		}

		public List<T> LoadWithEntities<T>(EntitySet entities)
			where T : class
		{
			// If no entity was found, return an empty array
			if (entities.Count == 0)
				return new List<T>();

			// Create an empty array (initialized with the number of found entities)
			List<T> result = new List<T>();

			// Use a for loop as the Enumerator looses the order
			for (int i = 0; i < entities.Count; i++)
			{
				Entity entity = entities[i];

				// Gets a wrapper over this entity (a new one or from the cache)
				object persistable = GetInstance(entity);

				// If nothing went wrong add it to the result array
				if (persistable != null)
				{
					// Try to convert the entity in the desired type. If not possible, just ignore it
					if (persistable is T)
					{
						OnLoaded<T>((T)persistable);
						result.Add((T)persistable);
					}
				}
			}

			// Give back a strongly typed collection
			return result;
		}

		/// <summary>
		/// Loads a set of IPersistable objects using a <cref>Query</cref> instance
		/// </summary>
		/// <param name="query">The query.</param>
		/// <returns>The collection of the objects found</returns>
		public List<T> Load<T>(Query query)
			where T : class
		{
			_Factory.AddAssembly(typeof(T).Assembly);

			string[] attributes = new string[query.InferredAttributes.Count];
            if (query.InferredAttributes.Count > 0)
            {
                query.InferredAttributes.CopyTo(attributes, 0);
            }

            string orderby = String.Empty;

            if (query.OrderBy.Count > 0)
            {
                string[] orderbies = new string[query.OrderBy.Count];
                query.OrderBy.CopyTo(orderbies, 0);
                orderby = String.Join(",", orderbies);
            }

			EntitySet result;
			if (!(query is OQuery))
			{
				string opath = ObjectContext.ConvertNamespaceDomainToEuss(query.FromType, query.Constraint);

				if (oPathSwitch.Enabled)
				{
					System.Diagnostics.Trace.Write(opath);

					if (query.MaxResults > 0 || query.FirstResult > 1)
					{
						System.Diagnostics.Trace.Write(", Take " + query.MaxResults);
						System.Diagnostics.Trace.Write(", Skip " + (query.FirstResult - 1));
					}

					System.Diagnostics.Trace.WriteLine(String.Empty);
				}

				result = _PersistenceEngine.Load(opath, attributes, orderby, query.FirstResult, query.MaxResults);
			}
			else
			{
				OQuery oquery = (OQuery)query;

				if (oPathSwitch.Enabled)
				{
					System.Diagnostics.Trace.WriteLine(oquery.Constraint);
					System.Diagnostics.Trace.WriteLine("\tStartRowIndex : " + (query.FirstResult - 1));
					System.Diagnostics.Trace.WriteLine("\tMaximumRows : " + query.MaxResults);
				}

				result = _PersistenceEngine.Load(oquery.Query, attributes, orderby, query.FirstResult, query.MaxResults);
			}

			if (query.InferredRelationships.Count > 0)
			{
				string[] references = new string[query.InferredRelationships.Count];
				query.InferredRelationships.CopyTo(references, 0);

				_PersistenceEngine.LoadReference(result, references);

			}

			return LoadWithEntities<T>(result);
		}

		/// <summary>
		/// Loads only one IPersistable object using a <cref>Query</cref> instance
		/// If more than one objects match the query, return the first.
		/// </summary>
		/// <returns>the object found</returns>
		public T LoadSingle<T>()
			where T : class
		{
			return LoadSingle<T>(typeof(T));
		}

		/// <summary>
		/// Loads only one IPersistable object using a <cref>Query</cref> instance
		/// If more than one objects match the query, return the first.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns>the object found</returns>
		public T LoadSingle<T>(Type type)
			where T : class
		{
			return LoadSingle<T>(type, String.Empty);
		}

		/// <summary>
		/// Loads only one IPersistable object using a <cref>Query</cref> instance
		/// If more than one objects match the query, return the first.
		/// </summary>
		/// <param name="constraint">criterias that object must match (opath syntax)</param>
		/// <returns>the object found</returns>
		public T LoadSingle<T>(string constraint)
			where T : class
		{
			Query query = new Query(typeof(T), constraint);
			query.MaxResults = 1;
			List<T> result = Load<T>(query);
			return result.Count > 0 ? result[0] : default(T);
		}

		/// <summary>
		/// Loads only one IPersistable object using a <cref>Query</cref> instance
		/// If more than one objects match the query, return the first.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="constraint">criterias that object must match (opath syntax)</param>
		/// <returns>the object found</returns>
		public T LoadSingle<T>(Type type, string constraint)
			where T : class
		{
			Query query = new Query(type, constraint);
			query.MaxResults = 1;
			List<T> result = Load<T>(query);
			return result.Count > 0 ? result[0] : default(T);
		}

		/// <summary>
		/// Compute an arithmetic opath expression 
		/// </summary>
		/// <param name="opath">the opath, must start with one of these functions (count(), avg(), sum(), min(), max())</param>
		/// <returns>the result of the expression</returns>
		public object LoadScalar(string opath)
		{
			if (oPathSwitch.Enabled)
			{
				System.Diagnostics.Trace.WriteLine(opath);
			}

			return _PersistenceEngine.LoadScalar(opath);
		}

		/// <summary>
		/// Loads the entities with the specified type.
		/// </summary>
		/// <returns>The collection of the objects found</returns>
		public List<T> Load<T>()
			where T : class
		{
			return Load<T>(typeof(T));
		}

		/// <summary>
		/// Loads the entities with the specified type.
		/// </summary>
		/// <param name="constraint">criterias that object must match (opath syntax)</param>
		/// <returns>The collection of the objects found</returns>
		public List<T> Load<T>(string constraint)
			where T : class
		{
			return Load<T>(typeof(T), constraint);
		}

		/// <summary>
		/// Loads the entities with the specified type.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns>The collection of the objects found</returns>
		public List<T> Load<T>(Type type)
			where T : class
		{
			return Load<T>(new Query(type));
		}

		/// <summary>
		/// Loads the entities with the specified type.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="constraint">criterias that object must match (opath syntax)</param>
		/// <returns>The collection of the objects found</returns>
		public List<T> Load<T>(Type type, string constraint)
			where T : class
		{
			_Factory.AddAssembly(typeof(T).Assembly);

			string opath = ObjectContext.ConvertNamespaceDomainToEuss(type, constraint);

			if (oPathSwitch.Enabled)
			{
				System.Diagnostics.Trace.WriteLine(opath);
			}

			return LoadWithEntities<T>(_PersistenceEngine.Load(opath));
		}

		/// <summary>
		/// Loads the parent objects for a given relationship
		/// </summary>
		/// <param name="fromObject">The serialized child object whose parent must be loaded</param>
		/// <param name="role">The role between the parent and the child object</param>
		/// <returns></returns>
		public IList<T> LoadParents<T>(object fromObject, string role)
			where T : class
		{
			string id = String.Empty;

			IPersistable persistable = fromObject as IPersistable;

			if (persistable == null)
			{
				if (_GlobalProcessedObjects.ContainsKey(RuntimeHelpers.GetHashCode(fromObject)))
				{
					id = _GlobalProcessedObjects[RuntimeHelpers.GetHashCode(fromObject)].Id;
				}
				else
				{
					throw new ArgumentException("The argument is either null or is not a dynamic proxy", "fromObject");
				}
			}
			else
			{
				id = persistable.Entity.Id;
			}

			return Load<T>(typeof(T), String.Concat("[exists(", role, "[id('", id, "')])]"));
		}

		/// <summary>
		/// Loads an object from an existing id
		/// </summary>
		/// <param name="type">The type of the object to retrieve</param>
		/// <param name="id">The id of the object to retrieve</param>
		/// <returns>The objects with the corresponding ids</returns>
		public T LoadWithId<T>(string id)
			where T : class
		{
			// Load the entities corresponding to the id
			List<T> entities = LoadWithId<T>(new string[] { id });

			// If not entity was found, return an empty array
			if (entities.Count == 0)
				return default(T);

			return entities[0];
		}

		/// <summary>
		/// Loads some objects given their ids
		/// </summary>
		/// <param name="type">The type of the object to retrieve</param>
		/// <param name="ids">The ids of the objects to retrieve</param>
		/// <returns>The objects with the corresponding ids</returns>
		public List<T> LoadWithId<T>(string[] ids)
			where T : class
		{
			_Factory.AddAssembly(typeof(T).Assembly);
			return LoadWithEntities<T>(_PersistenceEngine.LoadWithId(ObjectContext.ConvertNamespaceDomainToEuss(typeof(T)), ids));
		}

		/// <summary>
		/// Loads all the referenced objects for a given entity and a role name. This is the Lazy Loading logic.
		/// </summary>
		/// <param name="entity">The Entity object from which the referenced items have to be loaded</param>
		/// <param name="name">The name of the role to load the linked entities from</param>
		/// <returns>The collection of the loaded objects</returns>
		public IList LoadReference(Entity entity, string name)
		{
			// Use the underlying Persistence Engine to Lazy Load the reference
			Entity container = new Entity(entity.Type);
			container.Id = entity.Id;
			_PersistenceEngine.LoadReference(new Entity[] { container }, new string[] { name });

			// Mark reference as "loaded"
			if (!entity.InferredReferences.Contains(name))
				entity.InferredReferences.Add(name);

			// If no entity was found, return an empty array
			if (container.MemberCount == 0)
				return new HashedList();

			// Create an empty array (initialized with the number of found entities)
			HashedList result = new HashedList();

			State state = entity.State;
			foreach (EntityEntry entry in container)
			{
				Entity e = (Entity)entry.Value;

				// Search this entity into the cache or create a new wrapper
				object instance = GetInstance(e);

				// Add the cached entry
				IPersistable persistable = instance as IPersistable;

				if (persistable != null)
				{
					entity.AddValue(name, persistable.Entity, typeof(Entity), State.UpToDate);
				}

				// If nothing went wrong add it to the result array
				result.Add(instance);
			}
			entity.State = state;

			// Give back a strongly typed collection
			return result;
		}

		public void TrackObject(Entity entity, object o)
		{
			// Can be null if it is initialized in the constructor and TrackChildren is called in a dynamic proxy
			if (o == null)
				return;

			if (!_TypeTable.ContainsKey(entity.Type))
				_TypeTable.Add(entity.Type, new IdentityMap());

			if (!_GlobalProcessedObjects.ContainsKey(RuntimeHelpers.GetHashCode(o)))
				_GlobalProcessedObjects.Add(RuntimeHelpers.GetHashCode(o), entity);

			IIdentityMap im = _TypeTable[entity.Type];
			im.Add(entity.Id, o);
		}

		/// <summary>
		/// Adds an IPersistable to the cache
		/// </summary>
		/// <param name="o">The object to add to the cache</param>
		public void TrackObject(IPersistable o)
		{
			// Can be null if it is initialized in the constructor and TrackChildren is called in a dynamic proxy
			if (o == null)
				return;

			if (!_TypeTable.ContainsKey(o.Entity.Type))
				_TypeTable.Add(o.Entity.Type, new IdentityMap());

			IIdentityMap im = _TypeTable[o.Entity.Type];

			if (im.Add(o))
			{
				o.ObjectContext = this;
				o.TrackChildren();
			}
		}

		/// <summary>
		/// Adds a collection of IPersistable to the cache
		/// </summary>
		/// <param name="pc">The objects to add to the cache</param>
		public void TrackObject<T>(IPersistableCollection<T> pc)
			where T : class
		{
			if (pc == null)
				return;

			pc.ObjectContext = this;

			if (pc.IsLoaded())
				foreach (object p in pc)
				{
					if (p is IPersistable)
						TrackObject((IPersistable)p);
				}
		}

		/// <summary>
		/// Adds a collection of IPersistable to the cache
		/// </summary>
		/// <param name="pc">The objects to add to the cache</param>
		public void TrackObject(IPersistableCollection pc)
		{
			if (pc == null)
				return;

			pc.ObjectContext = this;

			if (pc.IsLoaded())
				foreach (IPersistable p in pc)
					TrackObject(p);
		}

		/// <summary>
		/// Gets an object from the cache if it exists
		/// </summary>
		/// <param name="type">The type of the tracked entity</param>
		/// <param name="id">The identifier of the tracked entity</param>
		/// <returns>Null if the entity is not tracked</returns>
		public object GetTrackedObject(string type, string id)
		{
			if (_TypeTable.ContainsKey(type))
			{
				IIdentityMap im = _TypeTable[type];
				return im.Get(id);
			}

			return null;
		}

		/// <summary>
		/// Removes an IPersistable from the cache
		/// </summary>
		/// <param name="type">The type of the tracked entity</param>
		/// <param name="id">The identifier of the tracked entity</param>
		protected void UnTrackObject(string type, string id)
		{
			if (_TypeTable.ContainsKey(type))
			{
				IIdentityMap im = _TypeTable[type];
				im.Remove(id);
			}
		}

		/// <summary>
		/// Begins the transaction.
		/// </summary>
		public void BeginTransaction()
		{
			EnsureTransactionDisposed();

			_CurrentTransaction = new Transaction();

			if (_EnableEvents)
			{
				RegisterTransactionEvents();
			}

			_PendingObjects = new HashedList();
			_EntityGetter = new ReflectionEntityGetter(_Factory.PersistentDescriptor, _PersistenceEngine.Model);

			if (_EnableEvents)
			{
				OnTransactionOpened(_CurrentTransaction);
			}
		}

		#region Events

		#region Raising

		private void OnLoaded<T>(T item)
			where T : class
		{
			ServiceContext context = new ServiceContext(Operation.Loaded, EntityEventArgs.Empty);
			GetServices(Operation.Loaded).Visit(item, context);
			if (ObjectLoaded != null)
				ObjectLoaded(item, EntityEventArgs.Empty);
		}
		private void OnInserting<T>(T item, CancelEntityEventArgs e)
			where T : class
		{
			ServiceContext context = new ServiceContext(Operation.Inserting, e);
			GetServices(Operation.Inserting).Visit(item, context);
			if (ObjectInserting != null)
				ObjectInserting(item, e);
		}
		private void OnInserted<T>(T item)
			where T : class
		{
			ServiceContext context = new ServiceContext(Operation.Inserted, EntityEventArgs.Empty);
			GetServices(Operation.Inserted).Visit(item, context);
			if (ObjectInserted != null)
				ObjectInserted(item, EntityEventArgs.Empty);
		}
		private void OnUpdating<T>(T item, CancelEntityEventArgs e)
			where T : class
		{
			ServiceContext context = new ServiceContext(Operation.Updating, e);
			GetServices(Operation.Updating).Visit(item, context);
			if (ObjectUpdating != null)
				ObjectUpdating(item, e);
		}
		private void OnUpdated<T>(T item)
			where T : class
		{
			ServiceContext context = new ServiceContext(Operation.Updated, EntityEventArgs.Empty);
			GetServices(Operation.Updated).Visit(item, context);
			if (ObjectUpdated != null)
				ObjectUpdated(item, EntityEventArgs.Empty);
		}
		private void OnDeleting<T>(T item, CancelEntityEventArgs e)
			where T : class
		{
			ServiceContext context = new ServiceContext(Operation.Deleting, e);
			GetServices(Operation.Deleting).Visit(item, context);
			if (ObjectDeleting != null)
				ObjectDeleting(item, e);
		}
		private void OnDeleted<T>(T item)
			where T : class
		{
			ServiceContext context = new ServiceContext(Operation.Deleted, EntityEventArgs.Empty);
			GetServices(Operation.Deleted).Visit(item, context);
			if (ObjectDeleted != null)
				ObjectDeleted(item, EntityEventArgs.Empty);
		}

		private void OnRelationshipRemoving<T>(T item, ReferenceCancelEventArgs e)
			where T : class
		{
			GetServices(Operation.RemovingRelationship).Visit(item, e.Child, ReferenceServiceContext.Create(Operation.RemovingRelationship, e.Child, e));
			if (ObjectRelationRemoving != null)
				ObjectRelationRemoving(item, e);
		}

		private void OnRelationshipRemoved<T>(T item, ReferenceEventArgs e)
			where T : class
		{
			GetServices(Operation.RemovedRelationship).Visit(item, e.Child, ReferenceServiceContext.Create(Operation.RemovedRelationship, e.Child, e));
			if (ObjectRelationRemoved != null)
				ObjectRelationRemoved(item, e);
		}

		private void OnRelationshipCreating<T>(T item, ReferenceCancelEventArgs e)
			where T : class
		{
			GetServices(Operation.CreatingRelationship).Visit(item, e.Child, ReferenceServiceContext.Create(Operation.CreatingRelationship, e.Child, e));
			if (ObjectRelationCreating != null)
				ObjectRelationCreating(item, e);
		}

		private void OnRelationshipCreated<T>(T item, ReferenceEventArgs e)
			where T : class
		{
			GetServices(Operation.CreatedRelationship).Visit(item, e.Child, ReferenceServiceContext.Create(Operation.CreatedRelationship, e.Child, e));
			if (ObjectRelationCreated != null)
				ObjectRelationCreated(item, e);
		}

		private void OnTransactionOpened(Transaction t)
		{
			if (TransactionOpened != null)
				TransactionOpened(t, EntityEventArgs.Empty);
		}

		private void OnTransactionClosed()
		{
			if (TransactionClosed != null)
				TransactionClosed(this, EntityEventArgs.Empty);
		}

		#endregion

		private void RegisterTransactionEvents()
		{
			EnsureTransactionCreated();
			_CurrentTransaction.EntityCreated += new EussEventHandler(Transaction_EntityCreated);
			_CurrentTransaction.EntityCreating += new EussCancelEventHandler(Transaction_EntityCreating);
			_CurrentTransaction.EntityDeleted += new EussEventHandler(Transaction_EntityDeleted);
			_CurrentTransaction.EntityDeleting += new EussCancelEventHandler(Transaction_EntityDeleting);
			_CurrentTransaction.EntityRelationshipCreated += new EussReferenceEventHandler(Transaction_EntityRelationshipCreated);
			_CurrentTransaction.EntityRelationshipCreating += new EussCancelReferenceEventHandler(Transaction_EntityRelationshipCreating);
			_CurrentTransaction.EntityRelationshipRemoved += new EussReferenceEventHandler(Transaction_EntityRelationshipRemoved);
			_CurrentTransaction.EntityRelationshipRemoving += new EussCancelReferenceEventHandler(Transaction_EntityRelationshipRemoving);
			_CurrentTransaction.EntityUpdated += new EussEventHandler(Transaction_EntityUpdated);
			_CurrentTransaction.EntityUpdating += new EussCancelEventHandler(Transaction_EntityUpdating);
		}

		void Transaction_EntityUpdating(object sender, CancelEntityEventArgs e)
		{
			Entity entity = (Entity)sender;
			object o = sender;
			if (o != null)
			{
				o = GetTrackedObject(entity.Type, entity.Id);
				if (o == null)
					o = sender;
			}
			OnUpdating(o, e);
			if (e.EntityChanged && !e.Cancel)
			{
				if (!(o is IPersistable))
				{
					UnTrackObject(entity.Type, entity.Id);
					TrackObject(ExtractEntity(o), o);
				}
				else
					ExtractEntity(o);
			}
		}

		void Transaction_EntityUpdated(object sender, EntityEventArgs e)
		{
			Entity entity = (Entity)sender;
			object o = sender;
			if (o != null)
			{
				o = GetTrackedObject(entity.Type, entity.Id);
				if (o == null)
					o = sender;
			}
			OnUpdated(o);
		}

		void Transaction_EntityRelationshipRemoving(object sender, ReferenceCancelEventArgs e)
		{
			Entity parent = sender as Entity;
			object o = sender;
			if (sender != null)
				o = GetTrackedObject(parent.Type, parent.Id);
			Entity child = e.Child as Entity;
			if (child != null)
				e.Child = GetTrackedObject(child.Type, child.Id);
			OnRelationshipRemoving(o, e);
		}

		void Transaction_EntityRelationshipRemoved(object sender, ReferenceEventArgs e)
		{
			Entity parent = sender as Entity;
			object o = sender;
			if (sender != null)
				o = GetTrackedObject(parent.Type, parent.Id);
			Entity child = e.Child as Entity;
			if (child != null)
				e.Child = GetTrackedObject(child.Type, child.Id);
			OnRelationshipRemoved(o, e);
		}

		void Transaction_EntityRelationshipCreating(object sender, ReferenceCancelEventArgs e)
		{
			Entity parent = sender as Entity;
			object o = sender;
			if (sender != null)
				o = GetTrackedObject(parent.Type, parent.Id);
			Entity child = e.Child as Entity;
			if (child != null)
				e.Child = GetTrackedObject(child.Type, child.Id);
			OnRelationshipCreating(o, e);
		}

		void Transaction_EntityRelationshipCreated(object sender, ReferenceEventArgs e)
		{
			Entity parent = sender as Entity;
			object o = sender;
			if (sender != null)
				o = GetTrackedObject(parent.Type, parent.Id);
			Entity child = e.Child as Entity;
			if (child != null)
				e.Child = GetTrackedObject(child.Type, child.Id);
			OnRelationshipCreated(o, e);
		}

		void Transaction_EntityDeleting(object sender, CancelEntityEventArgs e)
		{
			Entity entity = (Entity)sender;
			object o = sender;
			if (o != null)
			{
				o = GetTrackedObject(entity.Type, entity.Id);
				if (o == null)
					o = sender;
			}
			OnDeleting(o, e);
		}

		void Transaction_EntityDeleted(object sender, EntityEventArgs e)
		{
			Entity entity = (Entity)sender;
			object o = sender;
			if (o != null)
			{
				o = GetTrackedObject(entity.Type, entity.Id);
				if (o == null)
					o = sender;
			}
			OnDeleted(o);
		}

		void Transaction_EntityCreating(object sender, CancelEntityEventArgs e)
		{
			Entity entity = (Entity)sender;
			object o = sender;
			if (o != null)
			{
				o = GetTrackedObject(entity.Type, entity.Id);
				if (o == null)
					o = sender;
			}
			OnInserting(o, e);
			if (e.EntityChanged && !e.Cancel)
			{
				if (!(o is IPersistable))
				{
					UnTrackObject(entity.Type, entity.Id);
					TrackObject(ExtractEntity(o), o);
				}
				else
					ExtractEntity(o);
			}
		}

		void Transaction_EntityCreated(object sender, EntityEventArgs e)
		{
			Entity entity = (Entity)sender;
			object o = sender;
			if (o != null)
			{
				o = GetTrackedObject(entity.Type, entity.Id);
				if (o == null)
					o = sender;
			}
			OnInserted(o);
		}

		#endregion

		/// <summary>
		/// Ensures the transaction is created.
		/// </summary>
		private void EnsureTransactionCreated()
		{
			if (_CurrentTransaction == null)
				throw new UniversalStorageException("BeginTransaction() must be called before");
		}

		/// <summary>
		/// Ensures the transaction is disposed.
		/// </summary>
		private void EnsureTransactionDisposed()
		{
			if (_CurrentTransaction != null)
				throw new UniversalStorageException("CommitTransaction() or RollbackTransaction() must be called before");
		}

		private void TrackLocallyProcessed(Dictionary<object, Entity> local)
		{
			foreach (KeyValuePair<object, Entity> kvp in local)
			{
				TrackObject(kvp.Value, kvp.Key);
			}
		}

		/// <summary>
		/// Commits the transaction.
		/// </summary>
		public void CommitTransaction()
		{
			EnsureTransactionCreated();
			Hashtable entityStateBeforeSerialization = new Hashtable();
			try
			{
				_EnableLazyLoading = false;
				ArrayList removedFromSerialization = new ArrayList();

				foreach (object item in _PendingObjects)
				{
					// Creates a new entity representing this "nude" object
					Dictionary<object, Entity> local = new Dictionary<object, Entity>();
					Entity e = _EntityGetter.Get(item, local, _GlobalProcessedObjects);

					TrackLocallyProcessed(local);
					_CurrentTransaction.Serialize(e);
				}
			}
			finally
			{
				_EnableLazyLoading = true;
			}

			try
			{
				_CurrentTransaction.Commit(_PersistenceEngine, EnableEvents);

				foreach (object item in _PendingObjects)
				{
					Entity e;
					if (item is IPersistable)
						e = ((IPersistable)item).Entity;
					else
						e = _GlobalProcessedObjects[RuntimeHelpers.GetHashCode(item)];
				}

				if (_CurrentTransaction.NewIds.Count > 0)
				{
					foreach (object item in _GlobalProcessedObjects)
					{
						Type targetType = item.GetType();

						FieldInfo idFieldInfo = null;

						if (_FieldsInfoCache.ContainsKey(targetType))
						{
							idFieldInfo = _FieldsInfoCache[targetType];
						}

						if (idFieldInfo == null)
						{

							Descriptors.PropertyDescriptor idPropertyDesc = _Factory.PersistentDescriptor.GetIdDescriptor(targetType);
							if (idPropertyDesc != null)
							{
								idFieldInfo = targetType.GetField(idPropertyDesc.FieldName,
									BindingFlags.NonPublic | BindingFlags.Instance);

								if (idFieldInfo != null)
								{
									_FieldsInfoCache.Add(targetType, idFieldInfo);
								}
							}
						}

						if (idFieldInfo != null)
						{
							Entity e = _GlobalProcessedObjects[RuntimeHelpers.GetHashCode(item)];
							idFieldInfo.SetValue(item, Convert.ChangeType(e.Id, idFieldInfo.FieldType));
						}
					}
				}
			}
			finally
			{
				_CurrentTransaction = null;
				_PendingObjects.Clear();
				OnTransactionClosed();
			}
		}

		/// <summary>
		/// Rolls back the transaction.
		/// </summary>
		public void RollBackTransaction()
		{
			_CurrentTransaction = null;
			OnTransactionClosed();
		}

		/// <summary>
		/// Adds a set of Command object to the current transaction
		/// </summary>
		public void ExecuteCommand(IEnumerable<Command> commands)
		{
			EnsureTransactionCreated();

			List<Command> list = new List<Command>(commands);
			_CurrentTransaction.PushCommand(list.ToArray());
		}

		/// <summary>
		/// Adds a Command object to the current transaction
		/// </summary>
		public void ExecuteCommand(Command command)
		{
			EnsureTransactionCreated();
			_CurrentTransaction.PushCommand(new Command[] { command });
		}

		private void CreateRelationship(Entity parent, Entity child, string role)
		{
			EnsureTransactionCreated();

			CreateReferenceCommand command = new CreateReferenceCommand(role, parent.Id, parent.Type, child.Id, child.Type);
			_CurrentTransaction.PushCommand(new Command[] { command });
			_CurrentTransaction.AddConcernedEntity(parent);
			_CurrentTransaction.AddConcernedEntity(child);
		}

		/// <summary>
		/// Creates a relationship. Be careful, Parent and Child objects must have their Id property initialized !
		/// </summary>
		/// <param name="parent">Parent.</param>
		/// <param name="child">Child.</param>
		/// <param name="role">Role.</param>
		public void CreateRelationship(object parent, object child, string role)
		{
			try
			{
				_EnableLazyLoading = false;     // to be sure not to load unloaded references if not needed

				Dictionary<object, Entity> localProcessed = new Dictionary<object, Entity>();

				Entity parentEntity = parent is IPersistable
					? ((IPersistable)parent).Entity
					: _EntityGetter.Get(parent, localProcessed, _GlobalProcessedObjects);

				Entity childEntity = child is IPersistable
					? ((IPersistable)child).Entity
					: _EntityGetter.Get(child, localProcessed, _GlobalProcessedObjects);

				TrackLocallyProcessed(localProcessed);

				TrackObject(parentEntity, parent);
				TrackObject(childEntity, child);

				CreateRelationship(parentEntity, childEntity, role);
			}
			finally
			{
				_EnableLazyLoading = true;
			}
		}

		private void RemoveRelationship(Entity parent, Entity child, string role)
		{
			EnsureTransactionCreated();

			DeleteReferenceCommand command = new DeleteReferenceCommand(role, parent.Id, parent.Type, child.Id, child.Type);
			_CurrentTransaction.PushCommand(new Command[] { command });
			_CurrentTransaction.AddConcernedEntity(parent);
			_CurrentTransaction.AddConcernedEntity(child);
		}

		/// <summary>
		/// Removes a relationship.
		/// </summary>
		/// <param name="parent">Parent.</param>
		/// <param name="child">Child.</param>
		/// <param name="role">Role.</param>
		public void RemoveRelationship(object parent, object child, string role)
		{
			Dictionary<object, Entity> localProcessed = new Dictionary<object, Entity>();

			Entity parentEntity = parent is IPersistable
				? ((IPersistable)parent).Entity
				: _EntityGetter.Get(parent, localProcessed, _GlobalProcessedObjects);

			Entity childEntity = child is IPersistable
				? ((IPersistable)child).Entity
				: _EntityGetter.Get(child, localProcessed, _GlobalProcessedObjects);

			TrackLocallyProcessed(localProcessed);
			TrackObject(parentEntity, parent);
			TrackObject(childEntity, child);

			RemoveRelationship(parentEntity, childEntity, role);
		}

		public void Attach<T>(T item)
		{
			Entity entity = ExtractEntity(item);
			TrackObject(entity, item);
		}

		/// <summary>
		/// Detaches the specified graph.
		/// </summary>
		/// <param name="graph">The graph.</param>
		/// <returns>A cloned object without any relation with a Persistence Engine</returns>
		/// <remarks>Only the loaded relationship are cloned during the process.</remarks>
		public T Detach<T>(T graph) where T : class
		{
			// By using BinarySerialization the graph is cloned.
			// Moreover as the ISerializable.GetObjectData() method is defined on each Proxy type,
			// the serialization type is overriden to be replaced by the original type.
			// Thus deserializing the stream generates an original type array.
			// IPersistableCollection is also ISerializable to replace its instances by ArrayList.

			BinaryFormatter formatter = new BinaryFormatter();
			MemoryStream ms = new MemoryStream();
			formatter.Serialize(ms, graph);
			ms.Position = 0;
			T obj = formatter.Deserialize(ms) as T;
			ms.Close();

			return obj;
		}

		/// <summary>
		/// Attaches a detached object.
		/// </summary>
		public void SerializeDetached(object item)
		{
			SerializeDetached(item, null);
		}

		/// <summary>
		/// Attaches a detached object.
		/// </summary>
		public void SerializeDetached(object item, string id)
		{
			if (id == null)
			{
				Evaluant.Uss.ObjectContext.Descriptors.PropertyDescriptor pd = _Factory.PersistentDescriptor.GetIdDescriptor(item.GetType());

				if (pd == null)
				{
					throw new ArgumentException("The object to attach must have an identifier. Please use SerializeDetached(object, string) instead.");
				}

				id = pd.GetValue(item.GetType(), item).ToString();
			}

			Dictionary<object, Entity> local = new Dictionary<object, Entity>();
			Entity e = _EntityGetter.Get(item, local, _GlobalProcessedObjects);

			e.Id = id;
			e.State = State.Modified;

			for (int i = e.EntityEntries.Count - 1; i >= 0; i--)
			{
				EntityEntry ee = (EntityEntry)e.EntityEntries[i];

				if (ee.IsEntity)
				{
					e.EntityEntries.Remove(ee);
				}
				else
				{
					ee.State = State.Modified;
				}
			}

			_CurrentTransaction.Serialize(e);
		}

		/// <summary>
		/// Imports the specified entity and its related.
		/// </summary>
		/// <param name="entity">Entity to import.</param>
		public void Import(object entity)
		{
			Import(entity as IPersistable);
		}

		/// <summary>
		/// Imports the specified entities and their related.
		/// </summary>
		/// <param name="entities">Entities to import.</param>
		/// <remarks>
		/// Only the related objects which are already loaded will be imported. 
		/// Thus it alows you to import only one node of the object graph.
		/// </remarks>
		public void Import(IEnumerable entities)
		{
			foreach (IPersistable entity in entities)
				Import(entity);
		}

		/// <summary>
		/// Determines whether the specified property is null.
		/// </summary>
		/// <param name="item">The entity.</param>
		/// <param name="property">The property.</param>
		/// <returns>
		/// 	<c>true</c> if the specified property is null; otherwise, <c>false</c>.
		/// </returns>
		public bool IsNull(object item, string property)
		{
			IPersistable persistable = item as IPersistable;

			if (persistable == null)
				throw new PersistenceManagerException("You can only use ObjectContext loaded objects");

			return persistable.Entity.IsNull(property);
		}

		/// <summary>
		/// Sets a property to null.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="property">The property.</param>
		public void SetNull(object item, string property)
		{
			IPersistable persistable = item as IPersistable;

			if (persistable == null)
				throw new PersistenceManagerException("You can only use ObjectContext loaded objects");

			persistable.Entity.SetValue(property, null);
		}

		/// <summary>
		/// Gets the id of an entity.
		/// </summary>
		/// <param name="entity">The entity.</param>
		/// <returns>The identifier of the entity</returns>
		public string GetId(object entity)
		{
			IPersistable persistable = entity as IPersistable;

			if (persistable != null)
			{
				return persistable.Entity.Id;
			}
			else if (_GlobalProcessedObjects.ContainsKey(RuntimeHelpers.GetHashCode(entity)))
			{
				return _GlobalProcessedObjects[RuntimeHelpers.GetHashCode(entity)].Id;
			}
			else
			{
				Evaluant.Uss.ObjectContext.Descriptors.PropertyDescriptor pd = Factory.PersistentDescriptor.GetIdDescriptor(entity.GetType());

				if (pd != null)
				{
					return Convert.ToString(pd.GetValue(entity.GetType(), entity));
				}

				throw new PersistenceManagerException("You can only use ObjectContext loaded objects");
			}
		}

		/// <summary>
		/// Sets the id of an entity.
		/// </summary>
		/// <param name="entity">The entity.</param>
		/// <param name="id">The id.</param>
		public void SetId(object entity, string id)
		{
			IPersistable persistable = entity as IPersistable;

			if (persistable != null)
			{
				persistable.Entity.Id = id;
			}
			else if (_GlobalProcessedObjects.ContainsKey(RuntimeHelpers.GetHashCode(entity)))
			{
				_GlobalProcessedObjects[RuntimeHelpers.GetHashCode(entity)].Id = id;
			}
			else
			{
				throw new PersistenceManagerException("You can only use ObjectContext loaded objects");
			}
		}

		/// <summary>
		/// Imports the specified entities and their related.
		/// </summary>
		/// <param name="entities">Entities to import.</param>
		/// <remarks>
		/// Only the related objects which are already loaded will be imported. 
		/// Thus it alows you to import only one node of the object graph.
		/// </remarks>
		public void Import<T>(IEnumerable<T> entities) where T : IPersistable
		{
			foreach (IPersistable entity in entities)
				Import(entity);
		}

		/// <summary>
		/// Imports the specified entity and its related.
		/// </summary>
		/// <param name="entity">Entity to import.</param>
		public void Import(IPersistable item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			// Checks if this objet has already been imported in this context
			if (GetTrackedObject(item.Entity.Type, item.Entity.Id) != null)
				return;

			item.Entity.State = State.New;

			// Registers this item and all related to the current ObjectContext
			TrackObject(item);

			foreach (EntityEntry entry in item.Entity)
			{
				entry.State = State.New;
				if (entry.IsEntity)
				{
					Entity related = (Entity)entry.Value;
					IPersistable relatedItem = GetTrackedObject(related.Type, related.Id) as IPersistable;
					if (related != relatedItem.Entity)
						throw new UniversalStorageException("Items should be the same.");

					Import(relatedItem);
				}
			}
		}

		#region Services

		private IDictionary<Operation, ServicesCollection> services = new Dictionary<Operation, ServicesCollection>();

		public void AddService(IService service, params Operation[] operations)
		{
			if (operations.Length == 0)
				operations = (Operation[])Enum.GetValues(typeof(Operation));
			foreach (Operation operation in operations)
			{
				if (services.ContainsKey(operation))
					services[operation].Add(service);
				else
				{
					ServicesCollection servicesCollection = new ServicesCollection();
					servicesCollection.Add(service);
					services.Add(operation, servicesCollection);
				}
			}
		}

		public void RemoveService(IService service, params Operation[] operations)
		{
			if (operations.Length > 0)
			{
				foreach (Operation operation in operations)
				{
					if (services.ContainsKey(operation))
						services[operation].Add(service);
					else
					{
						ServicesCollection servicesCollection = new ServicesCollection();
						servicesCollection.Add(service);
						services.Add(operation, servicesCollection);
					}
				}
			}
			else
			{
				foreach (ServicesCollection collection in services.Values)
				{
					if (collection.Contains(service))
						collection.Remove(service);
				}
			}
		}

		#endregion

		#region IServiceProvider Membres

		public object GetService(Type serviceType)
		{
			foreach (ServicesCollection collection in services.Values)
			{
				if (collection.Contains(serviceType))
					return collection[serviceType];
			}
			return null;
		}

		public ServicesCollection GetServices(Operation operation)
		{
			if (services.ContainsKey(operation))
				return services[operation];
			return null;
		}

		public T GetService<T>()
		{
			return (T)GetService(typeof(T));
		}

		#endregion
	}
}
